/*
 * Copyright (C) 2012 Artem Bakulin
 *
 * This file is part of Speech Fluency for Android
 * (http://code.google.com/p/speech-fluency-droid/)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.google.code.speechfluency;

import java.util.concurrent.atomic.AtomicInteger;

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder.AudioSource;
import android.util.Log;

/**
 * Worker thread for delayed audio feedback service
 * 
 * @author Artem Bakulin
 */
public class DelayedAudioWorker extends Thread {
    
    // Android spec guarantees that these values will be supported everywhere
    private static final int SAMPLE_RATE = 44100;
    private static final int FORMAT = AudioFormat.ENCODING_PCM_16BIT;
    private static final int IN_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
    private static final int OUT_CHANNEL = AudioFormat.CHANNEL_OUT_MONO;
    
    private static final AtomicInteger threadCounter = new AtomicInteger();
    
    private volatile boolean isRunning = true;
    
    private final AudioRecord record;
    
    private final AudioTrack track;
    
    private final short[] transferBuffer;
    
    private final float boostFactor;
    
    /**
     * Creates a new instance of DAF worker
     * @param settings general settings, like delay duration and microphone
     * boost level
     * @param onCall whether phone call is active (used to pick correct audio
     * source)
     */
    public DelayedAudioWorker(Settings settings, boolean onCall) {
        super("DelayedAudioThread-" + threadCounter.getAndIncrement());
        
        int internalBufferSize = Math.max(
                minimumBufferSize(),
                bufferSizeForDelay(settings.getDelayInSeconds()));
        
        this.record = new AudioRecord(
                onCall ? AudioSource.VOICE_UPLINK : AudioSource.DEFAULT,
                SAMPLE_RATE,
                IN_CHANNEL,
                FORMAT,
                internalBufferSize);
        
        this.track = new AudioTrack(
                AudioManager.STREAM_MUSIC,
                SAMPLE_RATE,
                OUT_CHANNEL,
                FORMAT,
                internalBufferSize,
                AudioTrack.MODE_STREAM);
        
        this.transferBuffer = new short[Math.min(internalBufferSize, 512)];
        
        this.boostFactor = boostFactor(settings.getBoostInDb());
    }
    
    @Override
    public void run() {
        try {
            Log.i(Logging.TAG, "Starting " + getName());
            record.startRecording();
            track.play();
            
            // Busy waiting is the only approach that worked on my HTC Desire S
            // Could not get OnRecordPositionUpdateListener callback working
            while (isRunning) {
                int read = record.read(transferBuffer, 0, transferBuffer.length);
                boost(transferBuffer, 0, read);
                track.write(transferBuffer, 0, read);
            }
            
            Log.i(Logging.TAG, getName() + " was stopped");
        } catch (Throwable t) {
            Log.e(Logging.TAG, "Unexpected exception in " + getName(), t);
        }
    }
    
    /**
     * Stops delayed audio feedback and disposes all resources
     */
    public void stopRecording() {
        Log.i(Logging.TAG, "Stopping " + getName());
        
        isRunning = false;
        record.stop();
        track.stop();
        
        record.release();
        track.release();
    }
    
    private void boost(short[] sample, int from, int to) {
        for (int i=from; i<to; i++ ){
            sample[i] *= boostFactor;
        }
    }
    
    private int bufferSizeForDelay(double delayInSeconds) {
        return (int)(SAMPLE_RATE * delayInSeconds);
    }
    
    private int minimumBufferSize() {
        int inputSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, IN_CHANNEL, FORMAT);
        int outputSize = AudioTrack.getMinBufferSize(SAMPLE_RATE, OUT_CHANNEL, FORMAT);
        return Math.max(inputSize, outputSize);
    }
    
    private float boostFactor(float boostInDb) {
        // boost in dB = 10 * log (boost factor)
        return (float)Math.pow(10.0, boostInDb/10.0);
    }
}
